﻿// ------------------------------------------------------------------------
// 版权信息
// 版权归重庆虫儿飞科技有限公司所有。
// 所有权利保留。
// 官方网站：https://netokit.com
// 许可证信息
// Neto.Kit 项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// 
// 使用条款
// 使用本代码应遵守相关法律法规和许可证的要求。
// 
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
// 
// 其他重要信息
// Neto.Kit 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// 有关 Neto.Kit 项目的其他详细信息，请参阅位于源代码树根目录中的 COPYRIGHT 和 DISCLAIMER 文件。
// 
// 更多信息
// 请访问 https://netokit.com 获取更多关于 Neto.Kit 项目的许可证和版权信息。
// ------------------------------------------------------------------------

using System.Web;

namespace Neto.Utils.Api;

/// <summary>
/// Api常用方法
/// </summary>
public static class ApiSignHelper
{
    /// <summary>
    /// 生成签名
    /// </summary>
    /// <param name="pars">参数</param>
    /// <param name="appSecret">密钥</param>
    /// <param name="signType">签名类型</param>
    /// <returns>包含签名信息的ApiSignOut对象</returns>
    public static ApiSignOut Sign(SortedDictionary<string, string> pars, string appSecret,
        ApiSignType signType = ApiSignType.SHA256)
    {
        return Sign(new ApiSignInput() { Pars = pars, AppSecret = appSecret, SignType = signType });
    }

    /// <summary>
    /// 生成签名
    /// </summary>
    /// <param name="input">签名输入参数</param>
    /// <returns>包含签名信息的ApiSignOut对象</returns>
    public static ApiSignOut Sign(ApiSignInput input)
    {
        // 校验安全密钥是否为空
        if (string.IsNullOrWhiteSpace(input.AppSecret)) throw new Exception("安全密钥不能为空");

        var apiSignOut = new ApiSignOut();
        apiSignOut.UrlStr = CreateLinkStringUrlencode(input);

        // 构造签名字符串并进行转换为小写
        apiSignOut.UrlStr = $"{apiSignOut.UrlStr}&key={StringUrlencode(input, input.AppSecret)}".ToLower();

        // 生成签名
        apiSignOut.Sign = SignStr(apiSignOut.UrlStr, input.SignType);

        return apiSignOut;
    }

    /// <summary>
    /// 验证 签名
    /// </summary>
    /// <param name="pars">参数</param>
    /// <param name="appSecret">密钥</param>
    /// <param name="inputSign">提交上来的签名</param>
    /// <param name="signType">签名类型</param>
    /// <returns>包含签名验证的ApiSignOut对象</returns>
    public static ApiSignOut IsSign(SortedDictionary<string, string> pars, string appSecret, string inputSign,
        ApiSignType signType = ApiSignType.SHA256)
    {
        return IsSign(new ApiSignInput() { Pars = pars, AppSecret = appSecret, Sign = inputSign, SignType = signType });
    }

    /// <summary>
    /// 验证 签名
    /// </summary>
    /// <param name="input">签名验证输入</param>
    /// <returns>包含签名验证的ApiSignOut对象</returns>
    public static ApiSignOut IsSign(ApiSignInput input)
    {
        // 校验安全密钥和签名是否为空
        if (string.IsNullOrWhiteSpace(input.AppSecret)) throw new Exception("安全密钥不能为空");
        if (string.IsNullOrWhiteSpace(input.Sign)) throw new Exception("签名不能为空");
        var apiSignOut = new ApiSignOut();
        apiSignOut.UrlStr = CreateLinkStringUrlencode(input);

        apiSignOut.UrlStr = $"{apiSignOut.UrlStr}&key={StringUrlencode(input, input.AppSecret)}";

        // 加密字符串生成签名
        apiSignOut.Sign = SignStr(apiSignOut.UrlStr, input.SignType);

        // 比较签名是否一致
        apiSignOut.IsVerSuccess = string.Equals(input.Sign.Trim(), apiSignOut.Sign.Trim(),
            StringComparison.CurrentCultureIgnoreCase);
        return apiSignOut;
    }


    /// <summary>
    /// 加密字符串
    /// </summary>
    /// <param name="str"></param>
    /// <param name="signType"></param>
    /// <returns></returns>
    public static string SignStr(string str, ApiSignType signType = ApiSignType.SHA256)
    {
        // 根据加密类型执行不同的加密算法
        return signType switch
        {
            ApiSignType.MD5 => str.ToLower().ToMd5(),
            ApiSignType.SHA256 => str.ToLower().ToSha256(),
            ApiSignType.SHA512 => str.ToLower().ToSha512(),
            _ => str.ToLower().ToSha256()
        };
    }

    /// <summary>
    /// 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串，并对参数值做urlencode
    /// </summary>
    /// <param name="input">输入</param>
    /// <returns>拼接完成以后的字符串</returns>
    public static string CreateLinkStringUrlencode(ApiSignInput input)
    {
        var prestr = new StringBuilder();
        foreach (var temp in input.Pars)
            prestr.Append(temp.Key + "=" + StringUrlencode(input, temp.Value) + "&");
        //去掉最後一个&字符
        if (prestr.Length <= 0) return prestr.ToString();
        var nLen = prestr.Length;
        prestr.Remove(nLen - 1, 1);

        return prestr.ToString();
    }

    /// <summary>
    /// 对字符串进行编码
    /// </summary>
    /// <param name="input">签名输入参数</param>
    /// <param name="content">待编码的字符串</param>
    /// <returns>编码后的字符串</returns>
    public static string StringUrlencode(ApiSignInput input, string content)
    {
        // 根据编码类型进行URL编码
        return input.ApiSignEncodeType == ApiSignEncodeType.Default
            ? HttpUtility.UrlEncode(content, input.Encoding)
            : ApiSignRFC3986EncodeHelper.UrlEncode(content, input.Encoding);
    }
}